#include "jsco-qjs.hpp"

static int fib(int n) {
  if (n <= 0)
    return 0;
  else if (n == 1)
    return 1;
  else
    return fib(n - 1) + fib(n - 2);
}

static JSValue js_fib(JSContext *ctx, JSValueConst this_val, int argc,
                      JSValueConst *argv) {
  int n, res;
  if (JS_ToInt32(ctx, &n, argv[0]))
    return JS_EXCEPTION;
  res = fib(n);
  return JS_NewInt32(ctx, res);
}

static void js_point_finalizer(JSRuntime *rt, JSValue val) {
  JSPointData *s =
      static_cast<JSPointData *>(JS_GetOpaque(val, js_point_class_id));
  js_free_rt(rt, s);
}

static JSValue js_point_ctor(JSContext *ctx, JSValueConst new_target, int argc,
                             JSValueConst *argv) {
  JSPointData *s;
  JSValue obj = JS_UNDEFINED;
  JSValue proto;
  s = static_cast<JSPointData *>(js_mallocz(ctx, sizeof(*s)));
  if (!s) {
    return JS_EXCEPTION;
  }
  if (JS_ToInt32(ctx, &s->x, argv[0])) {
    goto fail;
  }
  if (JS_ToInt32(ctx, &s->y, argv[1]))
    goto fail;
  proto = JS_GetPropertyStr(ctx, new_target, "prototype");
  if (JS_IsException(proto))
    goto fail;
  obj = JS_NewObjectProtoClass(ctx, proto, js_point_class_id);
  JS_FreeValue(ctx, proto);
  if (JS_IsException(obj))
    goto fail;
  JS_SetOpaque(obj, s); // struct binding js prototype
  return obj;
fail:
  js_free(ctx, s);
  JS_FreeValue(ctx, obj);
  return JS_EXCEPTION;
}

static JSValue js_point_get_xy(JSContext *ctx, JSValueConst this_val,
                               int magic) {
  JSPointData *s = static_cast<JSPointData *>(
      JS_GetOpaque2(ctx, this_val, js_point_class_id));
  if (!s)
    return JS_EXCEPTION;
  if (magic == 0)
    return JS_NewInt32(ctx, s->x);
  else
    return JS_NewInt32(ctx, s->y);
}

static JSValue js_point_set_xy(JSContext *ctx, JSValueConst this_val,
                               JSValue val, int magic) {
  JSPointData *s = static_cast<JSPointData *>(
      JS_GetOpaque2(ctx, this_val, js_point_class_id));
  if (!s)
    return JS_EXCEPTION;
  int v;
  if (JS_ToInt32(ctx, &v, val)) {
    return JS_EXCEPTION;
  }
  if (magic == 0)
    s->x = v;
  else
    s->y = v;
  return JS_UNDEFINED;
}

static JSValue js_point_norm(JSContext *ctx, JSValueConst this_val, int argc,
                             JSValueConst *argv) {
  JSPointData *s = static_cast<JSPointData *>(
      JS_GetOpaque2(ctx, this_val, js_point_class_id));
  if (!s)
    return JS_EXCEPTION;
  return JS_NewFloat64(ctx, sqrt((double)s->x * s->x + (double)s->y * s->y));
}

int main(int argc, char **argv) {
  JSRuntime *rt = JS_NewRuntime();
  // js_std_init_handlers(rt);
  JSContext *ctx = JS_NewContext(rt);
  JSValue globalThis = JS_GetGlobalObject(ctx);

  // JS_AddIntrinsicBaseObjects(ctx);
  // JS_AddIntrinsicDate(ctx);
  // JS_AddIntrinsicEval(ctx);
  // JS_AddIntrinsicStringNormalize(ctx);
  // JS_AddIntrinsicRegExp(ctx);
  // JS_AddIntrinsicJSON(ctx);
  // JS_AddIntrinsicProxy(ctx);
  // JS_AddIntrinsicMapSet(ctx);
  // JS_AddIntrinsicTypedArrays(ctx);
  // JS_AddIntrinsicPromise(ctx);
  // JS_AddIntrinsicBigInt(ctx);

  // js_init_module_os(ctx, "os");
  // js_init_module_std(ctx, "std");
  // js_std_add_helpers(ctx, 0, 0);
  // // js_std_loop(ctx);

  JSValue args[1];
  JSValue inParam = JS_NewInt32(ctx, 12);
  args[0] = inParam;
  JSValue result = js_fib(ctx, globalThis, 1, args);
  int resultRaw;
  int ret = JS_ToInt32(ctx, &resultRaw, result);
  if (ret) {
    cerr << "convert jsvalue to int failed!" << ret << endl;
  }
  cout << "fib(12) = " << resultRaw << endl << endl;

  // JSValue conret = JS_Eval(ctx, script, strlen(script), "jsco-qjs", 0);
  // if (JS_IsException(conret)) {
  //   cerr << "face some error:" << endl << endl;
  // }

  JSValue point_proto, point_class;
  JS_NewClassID(&js_point_class_id);
  JS_NewClass(rt, js_point_class_id, &js_point_class);

  point_proto = JS_NewObject(ctx);
  JS_SetPropertyFunctionList(ctx, point_proto, js_point_proto_funcs,
                             countof(js_point_proto_funcs));

  point_class =
      JS_NewCFunction2(ctx, js_point_ctor, "Point", 2, JS_CFUNC_constructor, 0);
  JS_SetConstructor(ctx, point_class, point_proto);
  JS_SetClassProto(ctx, js_point_class_id, point_proto);

  const char *import = "import * as std from 'std';"
                       "import * as os from 'os';"
                       "globalThis.std = std;"
                       "globalThis.os = os;"
                       "const console = {};"
                       "console.log = v => std.printf(v);)";
  const char *script =
      "const p = new Point(); p.x = 1;p.y = 2;const pn = p.norm();";
  JSValue scriptRet = JS_Eval(ctx, script, strlen(script), "input.js",
                              JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
  js_module_set_import_meta(ctx, scriptRet, 1, 1);
  JSValue init_run = JS_EvalFunction(ctx, scriptRet);

  if (JS_IsException(scriptRet)) {
    js_std_dump_error(ctx);
    cerr << "js_eval script failed!" << endl;
    JSValue excVal = JS_GetException(ctx);
  }

  JSValue pn = JS_GetPropertyStr(ctx, globalThis, "pn");
  if (JS_IsException(pn)) {
    js_std_dump_error(ctx);
    cerr << "get pn failed!" << endl;
  } else {
    int pnraw;
    ret = JS_ToInt32(ctx, &pnraw, pn);
    if (ret) {
      cerr << "convert jsvalue to int failed!" << endl << endl;
    }
    cout << "Point(1,2).norm() = " << pnraw << endl << endl;
  }

  const char *testEva = "const console = {};"
                        "console.log = v => globalThis.std.printf(v + '\n');"
                        "console.log('opk');"
                        "for (let ke in globalThis) {"
                        "console.log('globalThis: ' + k);"
                        "}"
                        "for (let k in std) {"
                        "console.log('std: ' + k);"
                        "}"
                        "for (let k in os) {"
                        "console.log('os: ' + k);"
                        "}"
                        "globalThis.std.printf('quickjs so quick!!!\n');"
                        "globalThis.std.printf(globalThis + '\n');"
                        "let a = false;"
                        "os.setTimeout(() => { std.printf('AAA\n')}, 2000);"
                        "globalThis.std.printf(a + '\n');";
  JSValue teRet =
      JS_Eval(ctx, testEva, strlen(testEva), "test1", JS_EVAL_TYPE_MODULE | JS_EVAL_TYPE_GLOBAL);
  printf("loop\n");
  js_std_loop(ctx);

  int32_t len;
  int to_int_32;
  if ((to_int_32 = JS_ToInt32(ctx, &len, teRet))) {
    js_std_dump_error(ctx);
  }

  return 0;
}